package com.ankang.controller;

import com.ankang.entity.ChannelLevel;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * IO控制器（基于手册7.3节Modbus RTU协议实现Y2/Y3延时通断）
 */
@Getter
public class IOController {


    private static final Logger log = LoggerFactory.getLogger(IOController.class);
    // 1. 手册7.3节Modbus RTU协议常量
    private static final byte MODBUS_CMD_WRITE_COIL = 0x05; // 写单个线圈功能码
    private static final short COIL_Y2 = 0x0001; // Y2对应线圈地址（匹配调试工具00 01）
    private static final short COIL_Y3 = 0x0002; // Y3对应线圈地址（手册7.3.2节DI/DO地址映射）
    private static final short COIL_VALUE_CLOSE = (short) 0xFF00; // 闭合值（调试工具FF 00）
    private static final short COIL_VALUE_OPEN = 0x0000; // 断开值（调试工具00 00）
    private static final int DELAY_MS = 2000; // 延时断开时间（2秒）

    // 2. 通信基础参数（手册3.6节默认参数）
    private static final int READ_TIMEOUT_MS = 30000;
    private static final int RECONNECT_INTERVAL_MS = 2000;
    private static final int CONNECT_TIMEOUT_MS = 5000;
    private static final Charset DEVICE_CHARSET = Charset.forName("GBK"); // 备用编码（ASCII协议用）

    private final Queue<byte[]> responseQueue = new LinkedList<>();

    // 新增：每个通道独立的锁，确保指令串行执行
    private final Lock commandLock = new ReentrantLock();

    // 3. 通道IO配置（手册4.1节接口参数）
    private final String ioIp;
    private final int ioPort;
    private final int ioDeviceAddr;
    private final int channelId;

    // 4. 状态与资源管理
    private final AtomicBoolean running = new AtomicBoolean(false);
    private final AtomicBoolean ioTriggered = new AtomicBoolean(false);
    private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
    private Socket ioSocket;
    private InputStream ioIn;
    private OutputStream ioOut;

    /**
     * 构造器（初始化IO配置）
     */
    public IOController(ChannelLevel channelLevel) {
        this.ioIp = channelLevel.getIoIp();
        this.ioPort = channelLevel.getIoPort();
        this.ioDeviceAddr = channelLevel.getIoDeviceAddr();
        this.channelId = channelLevel.getChannelId();
        // 手册7.3节设备地址校验（1~255）
        Assert.hasText(ioIp, "IO IP不能为空（手册4.1节要求）");
        Assert.isTrue(ioPort > 0, "IO端口必须大于0（手册3.6节要求）");
        Assert.isTrue(ioDeviceAddr >= 1 && ioDeviceAddr <= 255, "IO设备地址必须1-255（手册7.3节要求）");
    }

    /**
     * 启动IO监听（保持原有DI上报功能）
     */
    public void startListening(Runnable onIoTriggered) {
        if (running.compareAndSet(false, true)) {
            new Thread(() -> {
                while (running.get()) {
                    try {
                        if (ioSocket == null || ioSocket.isClosed()) {
                            connectIoSocket();
                        }
                        InputStream ioIn = ioSocket.getInputStream();
                        byte[] buffer = new byte[1024];
                        int bytesRead;
                        try {
                            bytesRead = ioIn.read(buffer);
                        } catch (SocketTimeoutException e) {
                            log.debug("通道{} IO监听：{}ms无数据（手册7.3节协议）", channelId, READ_TIMEOUT_MS);
                            continue;
                        }
                        if (bytesRead > 0) {
                            byte[] responseData = Arrays.copyOf(buffer, bytesRead);
                            // 判断是否为Modbus指令应答（长度8字节，且包含功能码）
                            if (bytesRead == 8 && (responseData[1] == MODBUS_CMD_WRITE_COIL)) {
                                // 指令应答：暂存到队列，供verifyResponse读取
                                synchronized (responseQueue) {
                                    responseQueue.add(responseData);
                                    responseQueue.notify(); // 唤醒等待的验证线程
                                }
                            } else {
                                // DI上报数据：单独解析，不干扰指令验证
                                parseDiReport(responseData);
                                if (ioTriggered.compareAndSet(true, false) && onIoTriggered != null) {
                                    log.info("通道{} X1触发，执行读卡器回调", channelId);
                                    onIoTriggered.run();
                                }
                            }
                        }
                    } catch (IOException e) {
                        log.error("通道{} IO监听异常，{}ms后重连：", channelId, RECONNECT_INTERVAL_MS, e);
                        closeIoSocket();
                        try {
                            Thread.sleep(RECONNECT_INTERVAL_MS);
                        } catch (InterruptedException ie) {
                            Thread.currentThread().interrupt();
                            log.error("通道{} 重连线程中断", channelId, ie);
                        }
                    } catch (Exception e) {
                        Thread.currentThread().interrupt();
                        log.error("通道{} 监听线程中断", channelId, e);
                        break;
                    }
                }
                closeIoSocket();
                scheduler.shutdown();
                log.info("通道{} IO监听停止", channelId);
            }, "IO-Listener-Channel-" + channelId).start();
        }
    }

    /**
     * 核心修改：发送Y2/Y3延时通断指令（Modbus RTU协议）
     */
    public void sendIoCommand(Direction direction) {
        commandLock.lock();
        try {
            Assert.notNull(direction, "指令方向不能为空（Y2=向下/Y3=向右下）");
            if (ioSocket == null || ioSocket.isClosed() || ioOut == null) {
                throw new IllegalStateException("IO连接未建立，无法发送指令（手册故障处理建议）");
            }

            short targetCoil = (direction == Direction.DOWN) ? COIL_Y2 : COIL_Y3;
            String coilName = (direction == Direction.DOWN) ? "Y2" : "Y3";
            int maxRetries = 2; // 最大重试次数
            boolean commandSuccess = false;

            // 重试循环：解决临时通信失败
            for (int retry = 0; retry <= maxRetries; retry++) {
                try {
                    // 发送闭合指令
                    byte[] closeCmd = buildModbusCmd((byte) ioDeviceAddr, MODBUS_CMD_WRITE_COIL, targetCoil, COIL_VALUE_CLOSE);
                    ioOut.write(closeCmd);
                    ioOut.flush();
                    log.info("通道{} 发送{}闭合指令（第{}次）：{}",
                            channelId, coilName, retry + 1, bytesToHexString(closeCmd));

                    // 验证闭合应答（修复参数不匹配问题）
                    if (verifyResponse(closeCmd)) {
                        // 延时后发送断开指令
                        Thread.sleep(DELAY_MS);
                        byte[] openCmd = buildModbusCmd((byte) ioDeviceAddr, MODBUS_CMD_WRITE_COIL, targetCoil, COIL_VALUE_OPEN);
                        ioOut.write(openCmd);
                        ioOut.flush();
                        log.info("通道{} 发送{}断开指令：{}",
                                channelId, coilName, bytesToHexString(openCmd));
                        verifyResponse(openCmd); // 断开指令验证（不阻断流程）
                        commandSuccess = true;
                        break;
                    } else if (retry < maxRetries) {
                        log.warn("通道{} {}闭合指令验证失败，准备重试（剩余{}次）",
                                channelId, coilName, maxRetries - retry);
                        Thread.sleep(1000); // 重试间隔1秒
                    }
                } catch (IOException e) {
                    log.error("通道{} {}指令发送异常（第{}次）",
                            channelId, coilName, retry + 1, e);
                    if (retry < maxRetries) {
                        Thread.sleep(1000);
                    }
                }
            }

            if (!commandSuccess) {
                log.error("通道{} {}闭合指令多次失败，终止操作", channelId, coilName);
                // 可选：触发告警机制
            }

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("通道{} 指令执行中断", channelId, e);
        } finally {
            commandLock.unlock();
        }
    }


    /**
     * 辅助方法1：构建Modbus RTU指令（含CRC16校验）
     */
    private byte[] buildModbusCmd(byte deviceAddr, byte cmd, short coilAddr, short value) {
        // 指令结构：设备地址(1) + 功能码(1) + 线圈地址(2) + 数值(2) + CRC(2)
        byte[] frame = new byte[6];
        frame[0] = deviceAddr;
        frame[1] = cmd;
        frame[2] = (byte) (coilAddr >> 8); // 线圈地址高8位
        frame[3] = (byte) (coilAddr & 0xFF); // 线圈地址低8位
        frame[4] = (byte) (value >> 8); // 数值高8位
        frame[5] = (byte) (value & 0xFF); // 数值低8位

        // 计算CRC16校验（Modbus RTU标准算法）
        int crc = calculateCrc16(frame);
        byte[] cmdWithCrc = new byte[8];
        System.arraycopy(frame, 0, cmdWithCrc, 0, 6);
        cmdWithCrc[6] = (byte) (crc & 0xFF); // CRC低8位
        cmdWithCrc[7] = (byte) (crc >> 8); // CRC高8位
        return cmdWithCrc;
    }

    /**
     * 辅助方法2：验证Modbus应答（确保指令生效）
     */
    private boolean verifyResponse(byte[] sentCmd) throws IOException, InterruptedException {
        byte[] response = new byte[8];
        int bytesRead = 0;
        long startTime = System.currentTimeMillis();
        final long TIMEOUT_MS = 3000;
        byte targetAddr = sentCmd[0]; // 目标设备地址
        byte targetFunc = sentCmd[1]; // 目标功能码

        while (System.currentTimeMillis() - startTime < TIMEOUT_MS) {
            int available = ioIn.available();
            if (available > 0) {
                byte[] buffer = new byte[available];
                int read = ioIn.read(buffer);
                if (read > 0) {
                    // 过滤短数据和无关设备应答
                    if (read < 8) {
                        log.info("通道{} 忽略短数据：{}",
                                channelId, bytesToHexString(buffer, read));
                        continue;
                    }
                    // 匹配目标设备地址和功能码
                    if (buffer[0] == targetAddr && buffer[1] == targetFunc) {
                        bytesRead = Math.min(read, 8);
                        System.arraycopy(buffer, 0, response, 0, bytesRead);
                        break;
                    } else {
                        log.info("通道{} 忽略无关数据：{}",
                                channelId, bytesToHexString(buffer, read));
                    }
                }
            } else {
                Thread.sleep(100);
            }
        }

        // 验证结果
        if (bytesRead == 8 && Arrays.equals(response, sentCmd)) {
            log.info("通道{} 应答匹配：{}", channelId, bytesToHexString(response));
            return true;
        } else {
            log.error("通道{} 应答不匹配，发送指令：{}，应答（长度{}）：{}",
                    channelId, bytesToHexString(sentCmd), bytesRead,
                    bytesToHexString(response, bytesRead)); // 此处使用重载方法修复报错
            return false;
        }
    }

    /**
     * 辅助方法3：CRC16校验计算（Modbus RTU标准）
     */
    private int calculateCrc16(byte[] data) {
        int crc = 0xFFFF;
        for (byte b : data) {
            crc ^= (b & 0xFF);
            for (int i = 0; i < 8; i++) {
                crc = (crc & 0x0001) != 0 ? (crc >> 1) ^ 0xA001 : crc >> 1;
            }
        }
        return crc;
    }


    /**
     * 修复报错：新增支持指定长度的字节数组转十六进制方法
     */
    private String bytesToHexString(byte[] bytes, int length) {
        if (bytes == null || length <= 0) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++) {
            sb.append(String.format("%02X ", bytes[i] & 0xFF));
        }
        return sb.toString().trim();
    }

    /**
     * 原方法保留（兼容无长度参数的调用）
     */
    private String bytesToHexString(byte[] bytes) {
        return bytes != null ? bytesToHexString(bytes, bytes.length) : "";
    }


    // ------------------------------ 原有方法保持不变 ------------------------------
    private void connectIoSocket() throws IOException {
        log.info("通道{} 连接IO设备：{}:{}", channelId, ioIp, ioPort);
        ioSocket = new Socket();
        // 调整连接超时为5秒，读取超时为10秒（根据设备手册调整）
        ioSocket.connect(new InetSocketAddress(ioIp, ioPort), 5000);
        ioSocket.setSoTimeout(10000); // 延长读取超时，避免过早断开
        ioIn = ioSocket.getInputStream();
        ioOut = ioSocket.getOutputStream();
        log.info("通道{} IO设备连接成功", channelId);
    }

    private void closeIoSocket() {
        try {
            if (ioIn != null) ioIn.close();
            if (ioOut != null) ioOut.close();
            if (ioSocket != null && !ioSocket.isClosed()) ioSocket.close();
        } catch (IOException e) {
            log.warn("通道{} 关闭IO连接异常：", channelId, e);
        } finally {
            ioIn = null;
            ioOut = null;
            ioSocket = null;
        }
    }

    private void parseDiReport(byte[] responseData) {
        log.info("通道{} 接收到DI上报数据：{}", channelId, bytesToHexString(responseData));
        try {
            if (responseData.length < 15) {
                log.warn("通道{} 上报数据长度不足15字节（手册7.1节要求）", channelId);
                return;
            }
            // 校验自定义协议帧头帧尾（手册7.1节：0X48 0X3A开头，0X45 0X44结尾）
            if (responseData[0] != (byte) 0X48 || responseData[1] != (byte) 0X3A) {
                log.warn("通道{} 非自定义协议帧头（需0X48 0X3A）", channelId);
                return;
            }
            if (responseData[13] != (byte) 0X45 || responseData[14] != (byte) 0X44) {
                log.warn("通道{} 非自定义协议帧尾（需0X45 0X44）", channelId);
                return;
            }
            // 解析X1状态（手册7.1.1节：Byte5=X1状态，0X01=触发）
            int x1Status = responseData[4] & 0XFF;
            ioTriggered.set(x1Status == 0X01);
            log.info("通道{} X1状态：{}（0X01=触发）", channelId,
                    (x1Status == 0X01) ? "触发" : "未触发");
        } catch (Exception e) {
            log.error("通道{} DI上报解析异常：", channelId, e);
        }
    }

    public void stopListening() {
        running.set(false);
    }

    // 方向枚举（Y2=向下，Y3=向右下）
    public enum Direction {
        DOWN, RIGHT
    }
}